package com.pactera.asmp.server.service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import com.pactera.asmp.server.dao.*;
import com.pactera.asmp.server.pojo.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.pactera.asmp.server.common.constants.Constants;
import com.pactera.asmp.server.common.constants.ResultCode;
import com.pactera.asmp.server.common.exception.CustomException;
import com.pactera.asmp.server.entity.ProjectModuleDTO;
import com.pactera.asmp.server.entity.ProjectModuleSearch;
import com.pactera.asmp.server.entity.ResponseResult;
import com.pactera.asmp.server.entity.UseCaseInfoSearchDTO;
import com.pactera.asmp.server.utils.CsvUtils;
import com.pactera.asmp.server.utils.ExcelUtils;
import com.pactera.asmp.server.utils.PageDataResult;

@Service
public class UseCaseServiceImpl implements UseCaseService {

    private static final Logger logger = LoggerFactory.getLogger(UseCaseServiceImpl.class);

    @Autowired
    private UseCaseInfoMapper useCaseInfoMapper;

    @Autowired
    private ScriptInfoMapper scriptInfoMapper;

    @Autowired
    private ProjectMapper projectMapper;

    @Autowired
    private ProjectModuleMapper projectModuleMapper;

    @Autowired
    private TestReportMapper mTestReportMapper;

    @Autowired
    private UserService userService;

    private static final String SUFFIX_CSV = "csv";

    private static final String SUFFIX_XLS = "xls";

    private static final String SUFFIX_XLSX = "xlsx";

    private static final List<String> PRIORITY_LIST = Lists.newArrayList("高,中,低".split(","));

    private static final String HEADER = "用例ID,用例名称,项目名称,优先级,关联脚本,前置条件,测试步骤,预期结果";

    private static final String HEADER_MODEL = "用例名称,优先级,前置条件,测试步骤,预期结果,项目名称,关联脚本";

    @Override
    public PageDataResult select(UseCaseInfoSearchDTO useCaseInfoSearchDTO) {
        logger.info(JSONObject.toJSONString(useCaseInfoSearchDTO));

        useCaseInfoSearchDTO.setProjectIds(userService.getRelatedProject());
        PageDataResult pageDataResult = new PageDataResult();
        PageHelper.startPage(useCaseInfoSearchDTO.getPage(), useCaseInfoSearchDTO.getLimit());
        useCaseInfoSearchDTO.setStatus(1);
        List<UseCaseInfo> list = useCaseInfoMapper.select(useCaseInfoSearchDTO);
        if(CollectionUtils.isEmpty(list)) {
            pageDataResult.setTotals(0);
            pageDataResult.setList(list);
            return pageDataResult;
        }
        // 获取关联脚本
        List<Long> useCaseIds = list.stream().map(UseCaseInfo::getId).distinct().collect(Collectors.toList());
        List<UseCaseScriptRel> rels = useCaseInfoMapper.selectRefsByUseCaseIds(useCaseIds);
        if(CollectionUtils.isNotEmpty(rels)) {
            Map<Long, List<UseCaseScriptRel>> relMap = rels.stream().collect(Collectors.groupingBy(UseCaseScriptRel::getUseCaseId));
            List<ScriptInfo2> scriptInfo2List = scriptInfoMapper.selectScriptByScriptIds(rels.stream().map(UseCaseScriptRel::getScriptId).distinct().collect(Collectors.toList()));
            Map<Integer, String> scriptNameMap = scriptInfo2List.stream().collect(Collectors.toMap(ScriptInfo2::getId, ScriptInfo2::getName));
            list.stream().forEach(useCaseInfo -> {
                List<UseCaseScriptRel> eachRels = relMap.getOrDefault(useCaseInfo.getId(), Lists.newArrayList());
                if(CollectionUtils.isNotEmpty(eachRels)) {
                    List<String> scrpitNames = new ArrayList<>();
                    for(UseCaseScriptRel eachRel :eachRels) {
                        String scriptName = scriptNameMap.get(eachRel.getScriptId());
                        if(StringUtils.isNotEmpty(scriptName)) {
                            scrpitNames.add(scriptName);
                        }
                    }
                    useCaseInfo.setScrpitIds(eachRels.stream().map(UseCaseScriptRel::getScriptId).distinct().collect(Collectors.toList()));
                    useCaseInfo.setScrpitNames(scrpitNames);
                }else {
                    useCaseInfo.setScrpitIds(Lists.newArrayList());
                    useCaseInfo.setScrpitNames(Lists.newArrayList());
                }
            });
        }else {
            list.stream().forEach(useCaseInfo -> {
                useCaseInfo.setScrpitIds(Lists.newArrayList());
                useCaseInfo.setScrpitNames(Lists.newArrayList());
            });
        }
        for(UseCaseInfo useCaseInfo : list){
            if(CollectionUtils.isEmpty(useCaseInfo.getScrpitIds())){
                useCaseInfo.setTestStatus(2);
                continue;
            }
            List<TestReport> testReports = mTestReportMapper.selectByScriptIds(useCaseInfo.getScrpitIds());
            if(CollectionUtils.isNotEmpty(testReports)){
                Integer firstTaskId = 0;
                Integer testStatus = 1;
                for(TestReport tr:testReports){
                    if(firstTaskId == 0){
                        firstTaskId = tr.getTaskId();
                    }else if(firstTaskId != tr.getTaskId()){
                        break;
                    }
                    if(tr.getStatus() == 0){
                        testStatus = 0;
                        break;
                    }
                }
                useCaseInfo.setTestStatus(testStatus);
            }else {
                useCaseInfo.setTestStatus(2);
            }
        }

        PageInfo<UseCaseInfo> pageInfo = new PageInfo<>(list);
        pageDataResult.setTotals(Long.valueOf(pageInfo.getTotal()).intValue());
        pageDataResult.setList(list);
        return pageDataResult;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UseCaseInfo insert(UseCaseInfo useCaseInfo) {
        logger.info(JSONObject.toJSONString(useCaseInfo));
        if(StringUtils.isNotEmpty(useCaseInfo.getPriority())) {
            if(!PRIORITY_LIST.contains(useCaseInfo.getPriority())) {
                throw new CustomException(ResultCode.PARAM_TYPE_BIND_ERROR);
            }
        }

        User currUser = (User) SecurityUtils.getSubject().getPrincipal();
        if (currUser == null) throw new CustomException(ResultCode.PERMISSION_EXPIRE);
        if(!checkNameExist(null, useCaseInfo.getName(), useCaseInfo.getProjectId())) {
            throw new CustomException(ResultCode.BUSINESS_USECASENAME_EXISTED);
        }
        if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitIds()) && !checkProjectIdMatchScriptIds(useCaseInfo.getProjectId(), useCaseInfo.getScrpitIds())) {
            throw new CustomException(ResultCode.DATA_IS_WRONG);
        }
        useCaseInfo.setUserId((long)currUser.getId());
        useCaseInfo.setTimeCreate(LocalDateTime.now());
        useCaseInfoMapper.insert(useCaseInfo);
        if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitIds())) {
            for(Integer scriptId :useCaseInfo.getScrpitIds()) {
                UseCaseScriptRel useCaseScriptRel = new UseCaseScriptRel();
                useCaseScriptRel.setUseCaseId(useCaseInfo.getId());
                useCaseScriptRel.setScriptId(scriptId);
                useCaseInfoMapper.insertRel(useCaseScriptRel);
            }
        }
        return useCaseInfo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean update(UseCaseInfo useCaseInfo) {
        logger.info(JSONObject.toJSONString(useCaseInfo));
        if(StringUtils.isNotEmpty(useCaseInfo.getPriority())) {
            if(!PRIORITY_LIST.contains(useCaseInfo.getPriority())) {
                throw new CustomException(ResultCode.PARAM_TYPE_BIND_ERROR);
            }
        }

        User currUser = (User) SecurityUtils.getSubject().getPrincipal();
        if (currUser == null) throw new CustomException(ResultCode.PERMISSION_EXPIRE);

        if(!checkNameExist(useCaseInfo.getId(), useCaseInfo.getName(), useCaseInfo.getProjectId())) {
            throw new CustomException(ResultCode.BUSINESS_USECASENAME_EXISTED);
        }
        if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitIds()) && !checkProjectIdMatchScriptIds(useCaseInfo.getProjectId(), useCaseInfo.getScrpitIds())) {
            throw new CustomException(ResultCode.DATA_IS_WRONG);
        }
        useCaseInfo.setUpdateUserId((long)currUser.getId());
        useCaseInfoMapper.deleteRelByUseCaseId(useCaseInfo.getId());
        if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitIds())) {
            for(Integer scriptId :useCaseInfo.getScrpitIds()) {
                UseCaseScriptRel useCaseScriptRel = new UseCaseScriptRel();
                useCaseScriptRel.setUseCaseId(useCaseInfo.getId());
                useCaseScriptRel.setScriptId(scriptId);
                useCaseInfoMapper.insertRel(useCaseScriptRel);
            }
        }
        return useCaseInfoMapper.update(useCaseInfo)>0;
    }

    @Override
    public boolean delete(Long id) {
        logger.info(id.toString());
        return useCaseInfoMapper.deleteByPrimaryKey(id, LocalDateTime.now())>0;
    }

    @Override
    public ResponseResult useCaseDetail(Long id) {
        UseCaseInfoSearchDTO useCaseInfoSearchDTO = new UseCaseInfoSearchDTO();
        useCaseInfoSearchDTO.setId(id);
        List<UseCaseInfo> list = useCaseInfoMapper.select(useCaseInfoSearchDTO);
        if(CollectionUtils.isEmpty(list))
            throw new CustomException(ResultCode.RESULT_DATA_NONE);
        UseCaseInfo useCaseInfo = list.get(0);
        // 获取关联脚本
        List<UseCaseScriptRel> rels = useCaseInfoMapper.selectRefsByUseCaseIds(Lists.newArrayList(useCaseInfo.getId()));
        if(CollectionUtils.isNotEmpty(rels)) {
            List<ScriptInfo2> scriptInfo2List = scriptInfoMapper.selectScriptByScriptIds(rels.stream().map(UseCaseScriptRel::getScriptId).distinct().collect(Collectors.toList()));
            useCaseInfo.setScriptList(scriptInfo2List);
        }
        return ResponseResult.defaultSuccess(useCaseInfo);
    }

    @Override
    @Transactional
    public ResponseResult importUseCases(MultipartFile file) {
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        if(!Lists.newArrayList(SUFFIX_CSV, SUFFIX_XLS, SUFFIX_XLSX).contains(suffix)) {
            throw new CustomException(ResultCode.FILE_ACCEPT_NOT_SUPPORT);
        }
        List<List<String>> dataList = null;
        if(StringUtils.equals(SUFFIX_CSV, suffix)) {
            dataList = CsvUtils.read(file);
        }else {
            dataList = ExcelUtils.read(suffix, file);
        }
        if(CollectionUtils.isEmpty(dataList)) {
            throw new CustomException(ResultCode.FILE_EMPTY);
        }
        UseCaseImportInfo useCaseImportInfo = new UseCaseImportInfo();
        useCaseImportInfo.setTotal(dataList.size());
        List<UseCaseInfo> useCaseInfos = new ArrayList<>();
        for(List<String> data: dataList) {
            if(null == data || data.size() < 6) {
                logger.error("行格式不正确。{}", JSONObject.toJSON(data));
                continue;
            }
            // 校验数据
            if(StringUtils.isEmpty(data.get(0))
                    || StringUtils.isEmpty(data.get(2))
                    || StringUtils.isEmpty(data.get(3))
                    || StringUtils.isEmpty(data.get(4))
                    || StringUtils.isEmpty(data.get(5))) {
                logger.error("{}。{}", ResultCode.PARAM_IS_BLANK.message(), JSONObject.toJSON(data));
                continue;
            }
            if(data.get(0).length() > 30) {
                logger.error("{}。{}", "名称过长", JSONObject.toJSON(data));
                continue;
            }
            if(!data.get(0).matches(Constants.REGEXP_FIRST_LAST_NOT_SPACE)) {
//                    && data.get(0).matches(Constants.REGEXP_FIRST_LAST_NOT_SPACE))) {
                logger.error("{}。{}", ResultCode.PARAM_TYPE_BIND_ERROR.message(), JSONObject.toJSON(data));
                continue;
            }
            if(StringUtils.isNotEmpty(data.get(1))) {
                if(!PRIORITY_LIST.contains(data.get(1))) {
                    logger.error("{}。{}", ResultCode.PARAM_TYPE_BIND_ERROR.message(), JSONObject.toJSON(data));
                    continue;
                }
            }
            UseCaseInfo useCaseInfo = new UseCaseInfo();
            useCaseInfo.setName(data.get(0));
            useCaseInfo.setPriority(data.get(1));
            useCaseInfo.setPreCondition(data.get(2));
            useCaseInfo.setTestProcedure(data.get(3));
            useCaseInfo.setExpectResult(data.get(4));
            useCaseInfo.setProjectName(data.get(5));
            if(data.size() == 7) {
                if(StringUtils.isNotEmpty(data.get(6))) {
                    useCaseInfo.setScrpitNames(Arrays.asList(data.get(6).split(";")));
                }
            }
            useCaseInfos.add(useCaseInfo);
        }
        if(CollectionUtils.isEmpty(useCaseInfos)) {
            useCaseImportInfo.setFailure(dataList.size());
            return ResponseResult.defaultSuccess(useCaseImportInfo);
        }
        // 校验项目名称是否存在
        List<String> projectNames = useCaseInfos.stream().map(UseCaseInfo::getProjectName).distinct().collect(Collectors.toList());
        List<ProjectInfo> projectInfos = projectMapper.selectByNames(projectNames);
        if(CollectionUtils.isEmpty(projectInfos)) {
            logger.error("项目名称不存在。{}", JSONObject.toJSON(useCaseInfos));
            useCaseImportInfo.setFailure(dataList.size());
            return ResponseResult.defaultSuccess(useCaseImportInfo);
        }
        Map<String, Integer> projectIdMap = projectInfos.stream().collect(Collectors.toMap(ProjectInfo::getProjectName, ProjectInfo::getId));

        int success= 0;
        User currUser = (User) SecurityUtils.getSubject().getPrincipal();
        if (currUser == null) throw new CustomException(ResultCode.PERMISSION_EXPIRE);
        List<UseCaseInfo> resultInfos = new ArrayList<>();
        for(UseCaseInfo useCaseInfo : useCaseInfos) {
            if(null == projectIdMap.get(useCaseInfo.getProjectName())) {
                logger.error("项目名称不存在。{}", JSONObject.toJSON(useCaseInfo));
                continue;
            }
            useCaseInfo.setProjectId(projectIdMap.get(useCaseInfo.getProjectName()));
            if(!checkNameExist(null, useCaseInfo.getName(), useCaseInfo.getProjectId())){
                logger.error("测试用例名称已存在。{}", JSONObject.toJSON(useCaseInfo));
                continue;
            }
            useCaseInfo.setUserId((long)currUser.getId());
            useCaseInfo.setTimeCreate(LocalDateTime.now());
            useCaseInfoMapper.insert(useCaseInfo);

            if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitNames())) {
                // 获取脚本ids
                List<ProjectModuleDTO> proModules = projectModuleMapper.select(new ProjectModuleSearch() {{setProjectId(useCaseInfo.getProjectId());}});
                if(CollectionUtils.isEmpty(proModules)) {
                    logger.error("测试项目不存在测试模块，无法关联脚本。{}", JSONObject.toJSON(useCaseInfo));
                }else {
                    List<ScriptInfo2> scriptInfos = scriptInfoMapper.getScriptByNamesAndProjecModuletIds(
                            useCaseInfo.getScrpitNames(), proModules.stream().map(ProjectModuleDTO::getId).collect(Collectors.toList()));
                    if(CollectionUtils.isNotEmpty(scriptInfos)) {
                        useCaseInfo.setScrpitNames(scriptInfos.stream().map(ScriptInfo2::getName).collect(Collectors.toList()));
                        for(ScriptInfo2 scriptInfo2 :scriptInfos) {
                            UseCaseScriptRel useCaseScriptRel = new UseCaseScriptRel();
                            useCaseScriptRel.setUseCaseId(useCaseInfo.getId());
                            useCaseScriptRel.setScriptId(scriptInfo2.getId());
                            useCaseInfoMapper.insertRel(useCaseScriptRel);
                        }
                    }
                }
            }
            resultInfos.add(useCaseInfo);
            success++;
        }
        useCaseImportInfo.setSuccess(success);
        useCaseImportInfo.setFailure(dataList.size() - success);
        useCaseImportInfo.setUseCases(resultInfos);
        return ResponseResult.defaultSuccess(useCaseImportInfo);
    }

    /**
     * 校验项目下名称是否重复
     * 
     * @param id
     * @param name
     * @param projectId
     * @return
     */
    private boolean checkNameExist(Long id, String name, Integer projectId) {
        UseCaseInfo useCaseInfo = useCaseInfoMapper.selectByNameAndProjectId(name, projectId);
        if(null == useCaseInfo || null == useCaseInfo.getId()) {
            return true;
        }
        if(null == id) {
            return false;
        }
        return id.longValue() == useCaseInfo.getId().longValue();
    }

    /**
     * 项目与关联脚本的校验
     * 
     * @param projectId
     * @param scriptIds
     * @return
     */
    private boolean checkProjectIdMatchScriptIds(Integer projectId, List<Integer> scriptIds) {

        // 获取模块信息
        List<ProjectModuleDTO> proModules = projectModuleMapper.select(new ProjectModuleSearch() {{setProjectId(projectId);}});
        if(CollectionUtils.isEmpty(proModules)) {
            return false;
        }
        List<ScriptInfo2> scriptInfos = scriptInfoMapper.getScriptByProjecModuletIds(proModules.stream().map(ProjectModuleDTO::getId).collect(Collectors.toList()));
        if(CollectionUtils.isEmpty(scriptInfos)) {
            return false;
        }
        return scriptIds.stream().noneMatch(scriptId -> {
            return scriptInfos.stream().noneMatch(scriptInfo -> scriptInfo.getId().intValue() == scriptId.intValue());
        });
    }

    @Override
    public void export(UseCaseInfoSearchDTO useCaseInfoSearchDTO, HttpServletResponse response) {
        try {
            useCaseInfoSearchDTO.setLimit(Integer.MAX_VALUE);
            useCaseInfoSearchDTO.setPage(1);
            PageDataResult pageDataResult = this.select(useCaseInfoSearchDTO);
            List<UseCaseInfo> useCaseInfoList= (List<UseCaseInfo>) pageDataResult.getList();
            List<List<String>> dataList = Lists.newArrayList();
            if(CollectionUtils.isNotEmpty(useCaseInfoList)) {
                dataList.addAll(useCaseInfoList.stream().map(useCaseInfo -> {
                    List<String> data = new ArrayList<>();
                    data.add(String.valueOf(useCaseInfo.getId()));
                    data.add(useCaseInfo.getName());
                    data.add(useCaseInfo.getProjectName());
                    data.add(useCaseInfo.getPriority());
                    if(CollectionUtils.isNotEmpty(useCaseInfo.getScrpitNames())) {
                        data.add(useCaseInfo.getScrpitNames().stream().collect(Collectors.joining(",")));
                    }else {
                        data.add("");
                    }
                    data.add(useCaseInfo.getPreCondition());
                    data.add(useCaseInfo.getTestProcedure());
                    data.add(useCaseInfo.getExpectResult());
                    return data;
                }).collect(Collectors.toList()));
            }
            ExcelUtils.writeExcel(response, Lists.newArrayList(HEADER.split(",")), dataList, "用例");
        }catch(Exception ex) {
            ExcelUtils.writeExcel(response, Lists.newArrayList(HEADER.split(",")), Lists.newArrayList(), "用例");
        }
    }

    @Override
    public void exportModel(HttpServletResponse response) {
        List<List<String>> dataList = Lists.newArrayList();
        dataList.add(Lists.newArrayList("调节空调温度3", "高", "上电", "启动车机", "正常调节", "开发测试"));
        dataList.add(Lists.newArrayList("调节空调温度4", "中", "上电", "启动车机", "正常调节", "开发测试"));
        dataList.add(Lists.newArrayList("调节空调温度5", "低", "上电", "启动车机", "正常调节", "开发测试"));
        ExcelUtils.writeExcel(response, Lists.newArrayList(HEADER_MODEL.split(",")), dataList, "用例模板");
    }
}
